/*
 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.lang.reflect.*;
import java.lang.ref.WeakReference;
import java.util.*;

import com.sun.java.swing.SwingUtilities3;

import sun.awt.SubRegionShowable;
import sun.java2d.SunGraphics2D;
import sun.java2d.pipe.hw.ExtendedBufferCapabilities;
import sun.awt.SunToolkit;
import sun.util.logging.PlatformLogger;

/**
 * A PaintManager implementation that uses a BufferStrategy for
 * rendering.
 *
 * @author Scott Violet
 */
class BufferStrategyPaintManager extends RepaintManager.PaintManager {
  //
  // All drawing is done to a BufferStrategy.  At the end of painting
  // (endPaint) the region that was painted is flushed to the screen
  // (using BufferStrategy.show).
  //
  // PaintManager.show is overriden to show directly from the
  // BufferStrategy (when using blit), if successful true is
  // returned and a paint event will not be generated.  To avoid
  // showing from the buffer while painting a locking scheme is
  // implemented.  When beginPaint is invoked the field painting is
  // set to true.  If painting is true and show is invoked we
  // immediately return false.  This is done to avoid blocking the
  // toolkit thread while painting happens.  In a similar way when
  // show is invoked the field showing is set to true, beginPaint
  // will then block until showing is true.  This scheme ensures we
  // only ever have one thread using the BufferStrategy and it also
  // ensures the toolkit thread remains as responsive as possible.
  //
  // If we're using a flip strategy the contents of the backbuffer may
  // have changed and so show only attempts to show from the backbuffer
  // if we get a blit strategy.
  //

  //
  // Methods used to create BufferStrategy for Applets.
  //
  private static Method COMPONENT_CREATE_BUFFER_STRATEGY_METHOD;
  private static Method COMPONENT_GET_BUFFER_STRATEGY_METHOD;

  private static final PlatformLogger LOGGER = PlatformLogger.getLogger(
      "javax.swing.BufferStrategyPaintManager");

  /**
   * List of BufferInfos.  We don't use a Map primarily because
   * there are typically only a handful of top level components making
   * a Map overkill.
   */
  private ArrayList<BufferInfo> bufferInfos;

  /**
   * Indicates <code>beginPaint</code> has been invoked.  This is
   * set to true for the life of beginPaint/endPaint pair.
   */
  private boolean painting;
  /**
   * Indicates we're in the process of showing.  All painting, on the EDT,
   * is blocked while this is true.
   */
  private boolean showing;

  //
  // Region that we need to flush.  When beginPaint is called these are
  // reset and any subsequent calls to paint/copyArea then update these
  // fields accordingly.  When endPaint is called we then try and show
  // the accumulated region.
  // These fields are in the coordinate system of the root.
  //
  private int accumulatedX;
  private int accumulatedY;
  private int accumulatedMaxX;
  private int accumulatedMaxY;

  //
  // The following fields are set by prepare
  //

  /**
   * Farthest JComponent ancestor for the current paint/copyArea.
   */
  private JComponent rootJ;
  /**
   * Location of component being painted relative to root.
   */
  private int xOffset;
  /**
   * Location of component being painted relative to root.
   */
  private int yOffset;
  /**
   * Graphics from the BufferStrategy.
   */
  private Graphics bsg;
  /**
   * BufferStrategy currently being used.
   */
  private BufferStrategy bufferStrategy;
  /**
   * BufferInfo corresponding to root.
   */
  private BufferInfo bufferInfo;

  /**
   * Set to true if the bufferInfo needs to be disposed when current
   * paint loop is done.
   */
  private boolean disposeBufferOnEnd;

  private static Method getGetBufferStrategyMethod() {
    if (COMPONENT_GET_BUFFER_STRATEGY_METHOD == null) {
      getMethods();
    }
    return COMPONENT_GET_BUFFER_STRATEGY_METHOD;
  }

  private static Method getCreateBufferStrategyMethod() {
    if (COMPONENT_CREATE_BUFFER_STRATEGY_METHOD == null) {
      getMethods();
    }
    return COMPONENT_CREATE_BUFFER_STRATEGY_METHOD;
  }

  private static void getMethods() {
    java.security.AccessController.doPrivileged(
        new java.security.PrivilegedAction<Object>() {
          public Object run() {
            try {
              COMPONENT_CREATE_BUFFER_STRATEGY_METHOD = Component.class.
                  getDeclaredMethod("createBufferStrategy",
                      new Class[]{int.class,
                          BufferCapabilities.class});
              COMPONENT_CREATE_BUFFER_STRATEGY_METHOD.
                  setAccessible(true);
              COMPONENT_GET_BUFFER_STRATEGY_METHOD = Component.class.
                  getDeclaredMethod("getBufferStrategy");
              COMPONENT_GET_BUFFER_STRATEGY_METHOD.setAccessible(true);
            } catch (SecurityException e) {
              assert false;
            } catch (NoSuchMethodException nsme) {
              assert false;
            }
            return null;
          }
        });
  }

  BufferStrategyPaintManager() {
    bufferInfos = new ArrayList<BufferInfo>(1);
  }

  //
  // PaintManager methods
  //

  /**
   * Cleans up any created BufferStrategies.
   */
  protected void dispose() {
    // dipose can be invoked at any random time. To avoid
    // threading dependancies we do the actual diposing via an
    // invokeLater.
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        java.util.List<BufferInfo> bufferInfos;
        synchronized (BufferStrategyPaintManager.this) {
          while (showing) {
            try {
              BufferStrategyPaintManager.this.wait();
            } catch (InterruptedException ie) {
            }
          }
          bufferInfos = BufferStrategyPaintManager.this.bufferInfos;
          BufferStrategyPaintManager.this.bufferInfos = null;
        }
        dispose(bufferInfos);
      }
    });
  }

  private void dispose(java.util.List<BufferInfo> bufferInfos) {
    if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
      LOGGER.finer("BufferStrategyPaintManager disposed",
          new RuntimeException());
    }
    if (bufferInfos != null) {
      for (BufferInfo bufferInfo : bufferInfos) {
        bufferInfo.dispose();
      }
    }
  }

  /**
   * Shows the specified region of the back buffer.  This will return
   * true if successful, false otherwise.  This is invoked on the
   * toolkit thread in response to an expose event.
   */
  public boolean show(Container c, int x, int y, int w, int h) {
    synchronized (this) {
      if (painting) {
        // Don't show from backbuffer while in the process of
        // painting.
        return false;
      }
      showing = true;
    }
    try {
      BufferInfo info = getBufferInfo(c);
      BufferStrategy bufferStrategy;
      if (info != null && info.isInSync() &&
          (bufferStrategy = info.getBufferStrategy(false)) != null) {
        SubRegionShowable bsSubRegion =
            (SubRegionShowable) bufferStrategy;
        boolean paintAllOnExpose = info.getPaintAllOnExpose();
        info.setPaintAllOnExpose(false);
        if (bsSubRegion.showIfNotLost(x, y, (x + w), (y + h))) {
          return !paintAllOnExpose;
        }
        // Mark the buffer as needing to be repainted.  We don't
        // immediately do a repaint as this method will return false
        // indicating a PaintEvent should be generated which will
        // trigger a complete repaint.
        bufferInfo.setContentsLostDuringExpose(true);
      }
    } finally {
      synchronized (this) {
        showing = false;
        notifyAll();
      }
    }
    return false;
  }

  public boolean paint(JComponent paintingComponent,
      JComponent bufferComponent, Graphics g,
      int x, int y, int w, int h) {
    Container root = fetchRoot(paintingComponent);

    if (prepare(paintingComponent, root, true, x, y, w, h)) {
      if ((g instanceof SunGraphics2D) &&
          ((SunGraphics2D) g).getDestination() == root) {
        // BufferStrategy may have already constrained the Graphics. To
        // account for that we revert the constrain, then apply a
        // constrain for Swing on top of that.
        int cx = ((SunGraphics2D) bsg).constrainX;
        int cy = ((SunGraphics2D) bsg).constrainY;
        if (cx != 0 || cy != 0) {
          bsg.translate(-cx, -cy);
        }
        ((SunGraphics2D) bsg).constrain(xOffset + cx, yOffset + cy,
            x + w, y + h);
        bsg.setClip(x, y, w, h);
        paintingComponent.paintToOffscreen(bsg, x, y, w, h,
            x + w, y + h);
        accumulate(xOffset + x, yOffset + y, w, h);
        return true;
      } else {
        // Assume they are going to eventually render to the screen.
        // This disables showing from backbuffer until a complete
        // repaint occurs.
        bufferInfo.setInSync(false);
        // Fall through to old rendering.
      }
    }
    // Invalid root, do what Swing has always done.
    if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
      LOGGER.finer("prepare failed");
    }
    return super.paint(paintingComponent, bufferComponent, g, x, y, w, h);
  }

  public void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
      int deltaX, int deltaY, boolean clip) {
    // Note: this method is only called internally and we know that
    // g is from a heavyweight Component, so no check is necessary as
    // it is in paint() above.
    //
    // If the buffer isn't in sync there is no point in doing a copyArea,
    // it has garbage.
    Container root = fetchRoot(c);

    if (prepare(c, root, false, 0, 0, 0, 0) && bufferInfo.isInSync()) {
      if (clip) {
        Rectangle cBounds = c.getVisibleRect();
        int relX = xOffset + x;
        int relY = yOffset + y;
        bsg.clipRect(xOffset + cBounds.x,
            yOffset + cBounds.y,
            cBounds.width, cBounds.height);
        bsg.copyArea(relX, relY, w, h, deltaX, deltaY);
      } else {
        bsg.copyArea(xOffset + x, yOffset + y, w, h, deltaX,
            deltaY);
      }
      accumulate(x + xOffset + deltaX, y + yOffset + deltaY, w, h);
    } else {
      if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
        LOGGER.finer("copyArea: prepare failed or not in sync");
      }
      // Prepare failed, or not in sync. By calling super.copyArea
      // we'll copy on screen. We need to flush any pending paint to
      // the screen otherwise we'll do a copyArea on the wrong thing.
      if (!flushAccumulatedRegion()) {
        // Flush failed, copyArea will be copying garbage,
        // force repaint of all.
        rootJ.repaint();
      } else {
        super.copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
      }
    }
  }

  public void beginPaint() {
    synchronized (this) {
      painting = true;
      // Make sure another thread isn't attempting to show from
      // the back buffer.
      while (showing) {
        try {
          wait();
        } catch (InterruptedException ie) {
        }
      }
    }
    if (LOGGER.isLoggable(PlatformLogger.Level.FINEST)) {
      LOGGER.finest("beginPaint");
    }
    // Reset the area that needs to be painted.
    resetAccumulated();
  }

  public void endPaint() {
    if (LOGGER.isLoggable(PlatformLogger.Level.FINEST)) {
      LOGGER.finest("endPaint: region " + accumulatedX + " " +
          accumulatedY + " " + accumulatedMaxX + " " +
          accumulatedMaxY);
    }
    if (painting) {
      if (!flushAccumulatedRegion()) {
        if (!isRepaintingRoot()) {
          repaintRoot(rootJ);
        } else {
          // Contents lost twice in a row, punt.
          resetDoubleBufferPerWindow();
          // In case we've left junk on the screen, force a repaint.
          rootJ.repaint();
        }
      }
    }

    BufferInfo toDispose = null;
    synchronized (this) {
      painting = false;
      if (disposeBufferOnEnd) {
        disposeBufferOnEnd = false;
        toDispose = bufferInfo;
        bufferInfos.remove(toDispose);
      }
    }
    if (toDispose != null) {
      toDispose.dispose();
    }
  }

  /**
   * Renders the BufferStrategy to the screen.
   *
   * @return true if successful, false otherwise.
   */
  private boolean flushAccumulatedRegion() {
    boolean success = true;
    if (accumulatedX != Integer.MAX_VALUE) {
      SubRegionShowable bsSubRegion = (SubRegionShowable) bufferStrategy;
      boolean contentsLost = bufferStrategy.contentsLost();
      if (!contentsLost) {
        bsSubRegion.show(accumulatedX, accumulatedY,
            accumulatedMaxX, accumulatedMaxY);
        contentsLost = bufferStrategy.contentsLost();
      }
      if (contentsLost) {
        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
          LOGGER.finer("endPaint: contents lost");
        }
        // Shown region was bogus, mark buffer as out of sync.
        bufferInfo.setInSync(false);
        success = false;
      }
    }
    resetAccumulated();
    return success;
  }

  private void resetAccumulated() {
    accumulatedX = Integer.MAX_VALUE;
    accumulatedY = Integer.MAX_VALUE;
    accumulatedMaxX = 0;
    accumulatedMaxY = 0;
  }

  /**
   * Invoked when the double buffering or useTrueDoubleBuffering
   * changes for a JRootPane.  If the rootpane is not double
   * buffered, or true double buffering changes we throw out any
   * cache we may have.
   */
  public void doubleBufferingChanged(final JRootPane rootPane) {
    if ((!rootPane.isDoubleBuffered() ||
        !rootPane.getUseTrueDoubleBuffering()) &&
        rootPane.getParent() != null) {
      if (!SwingUtilities.isEventDispatchThread()) {
        Runnable updater = new Runnable() {
          public void run() {
            doubleBufferingChanged0(rootPane);
          }
        };
        SwingUtilities.invokeLater(updater);
      } else {
        doubleBufferingChanged0(rootPane);
      }
    }
  }

  /**
   * Does the work for doubleBufferingChanged.
   */
  private void doubleBufferingChanged0(JRootPane rootPane) {
    // This will only happen on the EDT.
    BufferInfo info;
    synchronized (this) {
      // Make sure another thread isn't attempting to show from
      // the back buffer.
      while (showing) {
        try {
          wait();
        } catch (InterruptedException ie) {
        }
      }
      info = getBufferInfo(rootPane.getParent());
      if (painting && bufferInfo == info) {
        // We're in the process of painting and the user grabbed
        // the Graphics. If we dispose now, endPaint will attempt
        // to show a bogus BufferStrategy. Set a flag so that
        // endPaint knows it needs to dispose this buffer.
        disposeBufferOnEnd = true;
        info = null;
      } else if (info != null) {
        bufferInfos.remove(info);
      }
    }
    if (info != null) {
      info.dispose();
    }
  }

  /**
   * Calculates information common to paint/copyArea.
   *
   * @return true if should use buffering per window in painting.
   */
  private boolean prepare(JComponent c, Container root, boolean isPaint, int x, int y,
      int w, int h) {
    if (bsg != null) {
      bsg.dispose();
      bsg = null;
    }
    bufferStrategy = null;
    if (root != null) {
      boolean contentsLost = false;
      BufferInfo bufferInfo = getBufferInfo(root);
      if (bufferInfo == null) {
        contentsLost = true;
        bufferInfo = new BufferInfo(root);
        bufferInfos.add(bufferInfo);
        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
          LOGGER.finer("prepare: new BufferInfo: " + root);
        }
      }
      this.bufferInfo = bufferInfo;
      if (!bufferInfo.hasBufferStrategyChanged()) {
        bufferStrategy = bufferInfo.getBufferStrategy(true);
        if (bufferStrategy != null) {
          bsg = bufferStrategy.getDrawGraphics();
          if (bufferStrategy.contentsRestored()) {
            contentsLost = true;
            if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
              LOGGER.finer("prepare: contents restored in prepare");
            }
          }
        } else {
          // Couldn't create BufferStrategy, fallback to normal
          // painting.
          return false;
        }
        if (bufferInfo.getContentsLostDuringExpose()) {
          contentsLost = true;
          bufferInfo.setContentsLostDuringExpose(false);
          if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
            LOGGER.finer("prepare: contents lost on expose");
          }
        }
        if (isPaint && c == rootJ && x == 0 && y == 0 &&
            c.getWidth() == w && c.getHeight() == h) {
          bufferInfo.setInSync(true);
        } else if (contentsLost) {
          // We either recreated the BufferStrategy, or the contents
          // of the buffer strategy were restored.  We need to
          // repaint the root pane so that the back buffer is in sync
          // again.
          bufferInfo.setInSync(false);
          if (!isRepaintingRoot()) {
            repaintRoot(rootJ);
          } else {
            // Contents lost twice in a row, punt
            resetDoubleBufferPerWindow();
          }
        }
        return (bufferInfos != null);
      }
    }
    return false;
  }

  private Container fetchRoot(JComponent c) {
    boolean encounteredHW = false;
    rootJ = c;
    Container root = c;
    xOffset = yOffset = 0;
    while (root != null &&
        (!(root instanceof Window) &&
            !SunToolkit.isInstanceOf(root, "java.applet.Applet"))) {
      xOffset += root.getX();
      yOffset += root.getY();
      root = root.getParent();
      if (root != null) {
        if (root instanceof JComponent) {
          rootJ = (JComponent) root;
        } else if (!root.isLightweight()) {
          if (!encounteredHW) {
            encounteredHW = true;
          } else {
            // We've encountered two hws now and may have
            // a containment hierarchy with lightweights containing
            // heavyweights containing other lightweights.
            // Heavyweights poke holes in lightweight
            // rendering so that if we call show on the BS
            // (which is associated with the Window) you will
            // not see the contents over any child
            // heavyweights.  If we didn't do this when we
            // went to show the descendants of the nested hw
            // you would see nothing, so, we bail out here.
            return null;
          }
        }
      }
    }
    if ((root instanceof RootPaneContainer) &&
        (rootJ instanceof JRootPane)) {
      // We're in a Swing heavyeight (JFrame/JWindow...), use double
      // buffering if double buffering enabled on the JRootPane and
      // the JRootPane wants true double buffering.
      if (rootJ.isDoubleBuffered() &&
          ((JRootPane) rootJ).getUseTrueDoubleBuffering()) {
        // Whether or not a component is double buffered is a
        // bit tricky with Swing. This gives a good approximation
        // of the various ways to turn on double buffering for
        // components.
        return root;
      }
    }
    // Don't do true double buffering.
    return null;
  }

  /**
   * Turns off double buffering per window.
   */
  private void resetDoubleBufferPerWindow() {
    if (bufferInfos != null) {
      dispose(bufferInfos);
      bufferInfos = null;
      repaintManager.setPaintManager(null);
    }
  }

  /**
   * Returns the BufferInfo for the specified root or null if one
   * hasn't been created yet.
   */
  private BufferInfo getBufferInfo(Container root) {
    for (int counter = bufferInfos.size() - 1; counter >= 0; counter--) {
      BufferInfo bufferInfo = bufferInfos.get(counter);
      Container biRoot = bufferInfo.getRoot();
      if (biRoot == null) {
        // Window gc'ed
        bufferInfos.remove(counter);
        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
          LOGGER.finer("BufferInfo pruned, root null");
        }
      } else if (biRoot == root) {
        return bufferInfo;
      }
    }
    return null;
  }

  private void accumulate(int x, int y, int w, int h) {
    accumulatedX = Math.min(x, accumulatedX);
    accumulatedY = Math.min(y, accumulatedY);
    accumulatedMaxX = Math.max(accumulatedMaxX, x + w);
    accumulatedMaxY = Math.max(accumulatedMaxY, y + h);
  }


  /**
   * BufferInfo is used to track the BufferStrategy being used for
   * a particular Component.  In addition to tracking the BufferStrategy
   * it will install a WindowListener and ComponentListener.  When the
   * component is hidden/iconified the buffer is marked as needing to be
   * completely repainted.
   */
  private class BufferInfo extends ComponentAdapter implements
      WindowListener {

    // NOTE: This class does NOT hold a direct reference to the root, if it
    // did there would be a cycle between the BufferPerWindowPaintManager
    // and the Window so that it could never be GC'ed
    //
    // Reference to BufferStrategy is referenced via WeakReference for
    // same reason.
    private WeakReference<BufferStrategy> weakBS;
    private WeakReference<Container> root;
    // Indicates whether or not the backbuffer and display are in sync.
    // This is set to true when a full repaint on the rootpane is done.
    private boolean inSync;
    // Indicates the contents were lost during and expose event.
    private boolean contentsLostDuringExpose;
    // Indicates we need to generate a paint event on expose.
    private boolean paintAllOnExpose;


    public BufferInfo(Container root) {
      this.root = new WeakReference<Container>(root);
      root.addComponentListener(this);
      if (root instanceof Window) {
        ((Window) root).addWindowListener(this);
      }
    }

    public void setPaintAllOnExpose(boolean paintAllOnExpose) {
      this.paintAllOnExpose = paintAllOnExpose;
    }

    public boolean getPaintAllOnExpose() {
      return paintAllOnExpose;
    }

    public void setContentsLostDuringExpose(boolean value) {
      contentsLostDuringExpose = value;
    }

    public boolean getContentsLostDuringExpose() {
      return contentsLostDuringExpose;
    }

    public void setInSync(boolean inSync) {
      this.inSync = inSync;
    }

    /**
     * Whether or not the contents of the buffer strategy
     * is in sync with the window.  This is set to true when the root
     * pane paints all, and false when contents are lost/restored.
     */
    public boolean isInSync() {
      return inSync;
    }

    /**
     * Returns the Root (Window or Applet) that this BufferInfo references.
     */
    public Container getRoot() {
      return (root == null) ? null : root.get();
    }

    /**
     * Returns the BufferStartegy.  This will return null if
     * the BufferStartegy hasn't been created and <code>create</code> is
     * false, or if there is a problem in creating the
     * <code>BufferStartegy</code>.
     *
     * @param create If true, and the BufferStartegy is currently null, one will be created.
     */
    public BufferStrategy getBufferStrategy(boolean create) {
      BufferStrategy bs = (weakBS == null) ? null : weakBS.get();
      if (bs == null && create) {
        bs = createBufferStrategy();
        if (bs != null) {
          weakBS = new WeakReference<BufferStrategy>(bs);
        }
        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
          LOGGER.finer("getBufferStrategy: created bs: " + bs);
        }
      }
      return bs;
    }

    /**
     * Returns true if the buffer strategy of the component differs
     * from current buffer strategy.
     */
    public boolean hasBufferStrategyChanged() {
      Container root = getRoot();
      if (root != null) {
        BufferStrategy ourBS = null;
        BufferStrategy componentBS = null;

        ourBS = getBufferStrategy(false);
        if (root instanceof Window) {
          componentBS = ((Window) root).getBufferStrategy();
        } else {
          try {
            componentBS = (BufferStrategy)
                getGetBufferStrategyMethod().invoke(root);
          } catch (InvocationTargetException ite) {
            assert false;
          } catch (IllegalArgumentException iae) {
            assert false;
          } catch (IllegalAccessException iae2) {
            assert false;
          }
        }
        if (componentBS != ourBS) {
          // Component has a different BS, dispose ours.
          if (ourBS != null) {
            ourBS.dispose();
          }
          weakBS = null;
          return true;
        }
      }
      return false;
    }

    /**
     * Creates the BufferStrategy.  If the appropriate system property
     * has been set we'll try for flip first and then we'll try for
     * blit.
     */
    private BufferStrategy createBufferStrategy() {
      Container root = getRoot();
      if (root == null) {
        return null;
      }
      BufferStrategy bs = null;
      if (SwingUtilities3.isVsyncRequested(root)) {
        bs = createBufferStrategy(root, true);
        if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
          LOGGER.finer("createBufferStrategy: using vsynced strategy");
        }
      }
      if (bs == null) {
        bs = createBufferStrategy(root, false);
      }
      if (!(bs instanceof SubRegionShowable)) {
        // We do this for two reasons:
        // 1. So that we know we can cast to SubRegionShowable and
        //    invoke show with the minimal region to update
        // 2. To avoid the possibility of invoking client code
        //    on the toolkit thread.
        bs = null;
      }
      return bs;
    }

    // Creates and returns a buffer strategy.  If
    // there is a problem creating the buffer strategy this will
    // eat the exception and return null.
    private BufferStrategy createBufferStrategy(Container root,
        boolean isVsynced) {
      BufferCapabilities caps;
      if (isVsynced) {
        caps = new ExtendedBufferCapabilities(
            new ImageCapabilities(true), new ImageCapabilities(true),
            BufferCapabilities.FlipContents.COPIED,
            ExtendedBufferCapabilities.VSyncType.VSYNC_ON);
      } else {
        caps = new BufferCapabilities(
            new ImageCapabilities(true), new ImageCapabilities(true),
            null);
      }
      BufferStrategy bs = null;
      if (SunToolkit.isInstanceOf(root, "java.applet.Applet")) {
        try {
          getCreateBufferStrategyMethod().invoke(root, 2, caps);
          bs = (BufferStrategy) getGetBufferStrategyMethod().
              invoke(root);
        } catch (InvocationTargetException ite) {
          // Type is not supported
          if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
            LOGGER.finer("createBufferStratety failed",
                ite);
          }
        } catch (IllegalArgumentException iae) {
          assert false;
        } catch (IllegalAccessException iae2) {
          assert false;
        }
      } else {
        try {
          ((Window) root).createBufferStrategy(2, caps);
          bs = ((Window) root).getBufferStrategy();
        } catch (AWTException e) {
          // Type not supported
          if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
            LOGGER.finer("createBufferStratety failed",
                e);
          }
        }
      }
      return bs;
    }

    /**
     * Cleans up and removes any references.
     */
    public void dispose() {
      Container root = getRoot();
      if (LOGGER.isLoggable(PlatformLogger.Level.FINER)) {
        LOGGER.finer("disposed BufferInfo for: " + root);
      }
      if (root != null) {
        root.removeComponentListener(this);
        if (root instanceof Window) {
          ((Window) root).removeWindowListener(this);
        }
        BufferStrategy bs = getBufferStrategy(false);
        if (bs != null) {
          bs.dispose();
        }
      }
      this.root = null;
      weakBS = null;
    }

    // We mark the buffer as needing to be painted on a hide/iconify
    // because the developer may have conditionalized painting based on
    // visibility.
    // Ideally we would also move to having the BufferStrategy being
    // a SoftReference in Component here, but that requires changes to
    // Component and the like.
    public void componentHidden(ComponentEvent e) {
      Container root = getRoot();
      if (root != null && root.isVisible()) {
        // This case will only happen if a developer calls
        // hide immediately followed by show.  In this case
        // the event is delivered after show and the window
        // will still be visible.  If a developer altered the
        // contents of the window between the hide/show
        // invocations we won't recognize we need to paint and
        // the contents would be bogus.  Calling repaint here
        // fixs everything up.
        root.repaint();
      } else {
        setPaintAllOnExpose(true);
      }
    }

    public void windowIconified(WindowEvent e) {
      setPaintAllOnExpose(true);
    }

    // On a dispose we chuck everything.
    public void windowClosed(WindowEvent e) {
      // Make sure we're not showing.
      synchronized (BufferStrategyPaintManager.this) {
        while (showing) {
          try {
            BufferStrategyPaintManager.this.wait();
          } catch (InterruptedException ie) {
          }
        }
        bufferInfos.remove(this);
      }
      dispose();
    }

    public void windowOpened(WindowEvent e) {
    }

    public void windowClosing(WindowEvent e) {
    }

    public void windowDeiconified(WindowEvent e) {
    }

    public void windowActivated(WindowEvent e) {
    }

    public void windowDeactivated(WindowEvent e) {
    }
  }
}
